﻿using Newtonsoft.Json;
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using System;

namespace Inlycat
{
    /// <summary>
    /// Http模块
    /// </summary>

    public class Http : Singleton<Http>
    {
        public enum Method
        {
            Get,
            Post
        }

        /// <summary>
        /// 目标服务器地址
        /// </summary>
        public string host = "localhost:3000";

        /// <summary>
        /// 加载显示对象控制脚本
        /// </summary>
        public HttpLoadingCtrl loadingCtrl;

        /// <summary>
        /// 加载显示文字（默认）
        /// </summary>
        public string showLoadingText = "loading";

        /// <summary>
        /// 加载显示延时
        /// </summary>
        public float loadingDisplayDelay = 1;

        /// <summary>
        /// 请求队列
        /// </summary>
        private static List<Request> _requestQueue = new List<Request>();

        /// <summary>
        /// 当前请求
        /// </summary>
        private static Request _currRequest;

        /// <summary>
        /// 加载前执行方法
        /// </summary>
        private static Action _OnOneLoadFinishedBefore;

        /// <summary>
        /// 加载完成执行方法
        /// </summary>
        private static Action _OnOneLoadFinished;

        /// <summary>
        /// 地址模板
        /// </summary>
        private static readonly string URL_TEMPLATE_FOR_MODUEL = "http://{0}/{1}{2}{3}";

        /// <summary>
        /// 请求个数
        /// </summary>
        /// <returns></returns>
        public static int queueSize()
        {
            return _requestQueue == null ? 0 : (_requestQueue.Count + (_currRequest == null ? 0 : 1));
        }

        /// <summary>
        /// 设置加载前执行方法
        /// </summary>
        /// <param name="func"></param>
        public static void SetOnOneLoadFinishedBeforeFunc(Action func)
        {
            _OnOneLoadFinishedBefore = func;
        }

        /// <summary>
        /// 设置加载完成执行方法
        /// </summary>
        /// <param name="func"></param>
        public static void SetOnOneLoadFinishedFunc(Action func)
        {
            _OnOneLoadFinished = func;
        }

        /// <summary>
        /// 获取地址
        /// </summary>
        /// <param name="moduelName">模块名</param>
        /// <param name="actionName">方法名</param>
        /// <returns></returns>
        public static string GetUrl(string moduelName, string actionName)
        {
            return string.Format(URL_TEMPLATE_FOR_MODUEL, Instance.host, moduelName, (string.IsNullOrEmpty(moduelName) || string.IsNullOrEmpty(actionName)) ? "" : "/", actionName);
        }

        /**
         * 尝试下一个请求
         */
        private static void TryRequestNext()
        {
            if (_requestQueue.Count > 0 && _currRequest == null)
            {
                _currRequest = _requestQueue[0];
                _requestQueue.RemoveAt(0);

                _currRequest.DoRequest();
            }
        }

        /// <summary>
        /// 发起一个标准格式的Http请求(Get)
        /// </summary>
        /// <typeparam name="T">返回数据的类型</typeparam>
        /// <param name="moduelName">模块名</param>
        /// <param name="actionName">方法名</param>
        /// <param name="paramsDic">参数表</param>
        /// <param name="succCallback">请求成功回调</param>
        /// <param name="failCallback">请求失败回调</param>
        /// <param name="errCallback">请求错误回调</param>
        /// <param name="host">服务器地址</param>
        /// <param name="isShowLoading">是否显示加载</param>
        /// <param name="loadingText">显示的加载文字</param>
        public static void Get<T>(string moduelName, string actionName, Dictionary<string, object> paramsDic, Action<T> succCallback = null, Action<string> failCallback = null, Action<string> errCallback = null, string host = null, bool isShowLoading = true, string loadingText = null) where T : class
        {
            //Debug.Log(string.Format("mod:{0},act:{1},isShow:{2}", moduelName, actionName, isShowLoading));
            if (isShowLoading) showLoading(_currRequest != null ? _currRequest.loadingText : loadingText);
            _requestQueue.Add(new Request<T>(RequestType.JSON, Method.Get, string.Format(URL_TEMPLATE_FOR_MODUEL, string.IsNullOrEmpty(host) ? Instance.host : host, moduelName, ((string.IsNullOrEmpty(moduelName) || string.IsNullOrEmpty(actionName)) ? "" : "/"), actionName), paramsDic, succCallback, failCallback, errCallback, isShowLoading, loadingText));
            TryRequestNext();
        }

        /// <summary>
        /// 发起一个标准格式的Http请求(Post)
        /// </summary>
        /// <typeparam name="T">返回数据的类型</typeparam>
        /// <param name="moduelName">模块名</param>
        /// <param name="actionName">方法名</param>
        /// <param name="paramsDic">参数表</param>
        /// <param name="succCallback">请求成功回调</param>
        /// <param name="failCallback">请求失败回调</param>
        /// <param name="errCallback">请求错误回调</param>
        /// <param name="host">服务器地址</param>
        /// <param name="isShowLoading">是否显示加载</param>
        /// <param name="loadingText">显示的加载文字</param>
        public static void Post<T>(string moduelName, string actionName, Dictionary<string, object> paramsDic, Action<T> succCallback = null, Action<string> failCallback = null, Action<string> errCallback = null, string host = null, bool isShowLoading = true, string loadingText = null) where T : class
        {
            //Debug.Log(string.Format("mod:{0},act:{1},isShow:{2}", moduelName, actionName, isShowLoading));
            if (isShowLoading) showLoading(_currRequest != null ? _currRequest.loadingText : loadingText);
            _requestQueue.Add(new Request<T>(RequestType.JSON, Method.Post, string.Format(URL_TEMPLATE_FOR_MODUEL, string.IsNullOrEmpty(host) ? Instance.host : host, moduelName, ((string.IsNullOrEmpty(moduelName) || string.IsNullOrEmpty(actionName)) ? "" : "/"), actionName), paramsDic, succCallback, failCallback, errCallback, isShowLoading, loadingText));
            TryRequestNext();
        }

        /// <summary>
        /// 发起一个Http请求(Get)，返回字符串
        /// </summary>
        /// <param name="moduelName">模块名</param>
        /// <param name="actionName">方法名</param>
        /// <param name="paramsDic">参数表</param>
        /// <param name="succCallback">请求成功回调</param>
        /// <param name="failCallback">请求失败回调</param>
        /// <param name="errCallback">请求错误回调</param>
        /// <param name="host">服务器地址</param>
        /// <param name="isShowLoading">是否显示加载</param>
        /// <param name="loadingText">显示的加载文字</param>
        public static void GetText(string moduelName, string actionName, Dictionary<string, object> paramsDic, Action<string> succCallback = null, Action<string> failCallback = null, Action<string> errCallback = null, string host = null, bool isShowLoading = true, string loadingText = null)
        {
            //Debug.Log(string.Format("mod:{0},act:{1},isShow:{2}", moduelName, actionName, isShowLoading));
            if (isShowLoading) showLoading(_currRequest != null ? _currRequest.loadingText : loadingText);
            _requestQueue.Add(new Request<string>(RequestType.TEXT, Method.Get, string.Format(URL_TEMPLATE_FOR_MODUEL, string.IsNullOrEmpty(host) ? Instance.host : host, moduelName, ((string.IsNullOrEmpty(moduelName) || string.IsNullOrEmpty(actionName)) ? "" : "/"), actionName), paramsDic, succCallback, failCallback, errCallback, isShowLoading, loadingText));
            TryRequestNext();
        }

        /// <summary>
        /// 发起一个Http请求(Post)，返回字符串
        /// </summary>
        /// <param name="moduelName">模块名</param>
        /// <param name="actionName">方法名</param>
        /// <param name="paramsDic">参数表</param>
        /// <param name="succCallback">请求成功回调</param>
        /// <param name="failCallback">请求失败回调</param>
        /// <param name="errCallback">请求错误回调</param>
        /// <param name="host">服务器地址</param>
        /// <param name="isShowLoading">是否显示加载</param>
        /// <param name="loadingText">显示的加载文字</param>
        public static void PostText(string moduelName, string actionName, Dictionary<string, object> paramsDic, Action<string> succCallback = null, Action<string> failCallback = null, Action<string> errCallback = null, string host = null, bool isShowLoading = true, string loadingText = null)
        {
            //Debug.Log(string.Format("mod:{0},act:{1},isShow:{2}", moduelName, actionName, isShowLoading));
            if (isShowLoading) showLoading(_currRequest != null ? _currRequest.loadingText : loadingText);
            _requestQueue.Add(new Request<string>(RequestType.TEXT, Method.Post, string.Format(URL_TEMPLATE_FOR_MODUEL, string.IsNullOrEmpty(host) ? Instance.host : host, moduelName, ((string.IsNullOrEmpty(moduelName) || string.IsNullOrEmpty(actionName)) ? "" : "/"), actionName), paramsDic, succCallback, failCallback, errCallback, isShowLoading, loadingText));
            TryRequestNext();
        }

        /// <summary>
        /// 发起一个Http请求(Get),返回Bson
        /// </summary>
        /// <typeparam name="T">返回数据的类型</typeparam>
        /// <param name="moduelName">模块名</param>
        /// <param name="actionName">方法名</param>
        /// <param name="paramsDic">参数表</param>
        /// <param name="succCallback">请求成功回调</param>
        /// <param name="failCallback">请求失败回调</param>
        /// <param name="errCallback">请求错误回调</param>
        /// <param name="host">服务器地址</param>
        /// <param name="isShowLoading">是否显示加载</param>
        /// <param name="loadingText">显示的加载文字</param>
        public static void GetBson<T>(string moduelName, string actionName, Dictionary<string, object> paramsDic, Action<T> succCallback = null, Action<string> failCallback = null, Action<string> errCallback = null, string host = null, bool isShowLoading = true, string loadingText = null) where T : class
        {
            //Debug.Log(string.Format("mod:{0},act:{1},isShow:{2}", moduelName, actionName, isShowLoading));
            if (isShowLoading) showLoading(_currRequest != null ? _currRequest.loadingText : loadingText);
            _requestQueue.Add(new Request<T>(RequestType.BSON, Method.Get, string.Format(URL_TEMPLATE_FOR_MODUEL, string.IsNullOrEmpty(host) ? Instance.host : host, moduelName, ((string.IsNullOrEmpty(moduelName) || string.IsNullOrEmpty(actionName)) ? "" : "/"), actionName), paramsDic, succCallback, failCallback, errCallback, isShowLoading, loadingText));
            TryRequestNext();
        }

        /// <summary>
        /// 发起一个Http请求(Post),返回Bson
        /// </summary>
        /// <typeparam name="T">返回数据的类型</typeparam>
        /// <param name="moduelName">模块名</param>
        /// <param name="actionName">方法名</param>
        /// <param name="paramsDic">参数表</param>
        /// <param name="succCallback">请求成功回调</param>
        /// <param name="failCallback">请求失败回调</param>
        /// <param name="errCallback">请求错误回调</param>
        /// <param name="host">服务器地址</param>
        /// <param name="isShowLoading">是否显示加载</param>
        /// <param name="loadingText">显示的加载文字</param>
        public static void PostBson<T>(string moduelName, string actionName, Dictionary<string, object> paramsDic, Action<T> succCallback = null, Action<string> failCallback = null, Action<string> errCallback = null, string host = null, bool isShowLoading = true, string loadingText = null) where T : class
        {
            //Debug.Log(string.Format("mod:{0},act:{1},isShow:{2}", moduelName, actionName, isShowLoading));
            if (isShowLoading) showLoading(_currRequest != null ? _currRequest.loadingText : loadingText);
            _requestQueue.Add(new Request<T>(RequestType.BSON, Method.Post, string.Format(URL_TEMPLATE_FOR_MODUEL, string.IsNullOrEmpty(host) ? Instance.host : host, moduelName, ((string.IsNullOrEmpty(moduelName) || string.IsNullOrEmpty(actionName)) ? "" : "/"), actionName), paramsDic, succCallback, failCallback, errCallback, isShowLoading, loadingText));
            TryRequestNext();
        }

        public static IEnumerator DoRequest<T>(Method method, RequestType requestType, string url, Dictionary<string, object> paramsDic, Action<T> succCallback, Action<string> failCallback, Action<string> errCallback, bool isShowLoading, string loadingText) where T : class
        {
            Debug.Log(string.Format("Http.{0}<{2}>({1}) ", method, url, requestType));
            if (isShowLoading)
            {
                showLoading(loadingText);
            }
            else
            {
                foreach (var request in _requestQueue)
                {
                    if (request.isShowLoading)
                    {
                        showLoading(loadingText);
                        break;
                    }
                }
            }

            WWW www;

            if (method == Method.Get)
            {
                if (paramsDic != null)
                {
                    List<string> l = new List<string>();
                    foreach (var o in paramsDic)
                    {
                        l.Add(string.Format("{0}={1}", o.Key, o.Value));
                    }
                    if (l.Count > 0)
                    {
                        url = string.Format("{0}?{1}", url, String.Join("&", l.ToArray()));
                    }
                }
                www = new WWW(url);
            }
            else
            {
                WWWForm form = new WWWForm();
                form.AddField("ox", "xo");
                if (paramsDic != null)
                {
                    foreach (var o in paramsDic)
                    {
                        if (o.Value is int)
                        {
                            form.AddField(o.Key, (int)o.Value);
                        }
                        else
                        {
                            form.AddField(o.Key, o.Value.ToString());
                        }
                    }
                }
                www = new WWW(url, form);
            }
            yield return www;

            CheckToHideLoading();
            if (string.IsNullOrEmpty(www.error))
            {
                Debug.Log(JsonConvert.SerializeObject(www.responseHeaders));
                switch (requestType)
                {
                    case RequestType.BSON:
                    case RequestType.JSON:
                        HttpResult<T> result = null;
                        if (requestType == RequestType.JSON)
                            result = JsonConvert.DeserializeObject<HttpResult<T>>(www.text);
                        else if (requestType == RequestType.BSON)
                            result = BsonConvert.DeserializeObject<HttpResult<T>>(www.bytes);
                        switch (result.result)
                        {
                            case HttpResult<T>.ResType.SUCCESS:
                                if (_OnOneLoadFinishedBefore != null)
                                {
                                    _OnOneLoadFinished();
                                }
                                succCallback(result.data);
                                if (_OnOneLoadFinished != null)
                                {
                                    _OnOneLoadFinished();
                                }
                                _currRequest = null;
                                TryRequestNext();
                                break;
                            case HttpResult<T>.ResType.FAILED:
                                if (result.error == null)
                                {
                                    if (failCallback != null)
                                        failCallback(result.err);
                                    else
                                        CommonFailedHalder(url, result.err);

                                    _currRequest = null;
                                    TryRequestNext();
                                }
                                else
                                {
                                    if (errCallback != null)
                                        errCallback(result.error);
                                    else
                                        CommonErrorHalder(url, result.error);
                                }
                                break;
                            case HttpResult<T>.ResType.NEED_AUTH:
                                //TODO 登录提示
                                Debug.Log("NEED_AUTH");
                                StopAllLoad();
                                break;
                            case HttpResult<T>.ResType.WAIT:
                                //TODO 排队
                                Debug.Log("WAIT");
                                StopAllLoad();
                                break;
                        }
                        break;
                    case RequestType.TEXT:
                        if (_OnOneLoadFinishedBefore != null)
                        {
                            _OnOneLoadFinishedBefore();
                        }

                        T objStr = www.text as T;
                        succCallback(objStr);

                        if (_OnOneLoadFinished != null)
                        {
                            _OnOneLoadFinished();
                        }

                        _currRequest = null;
                        TryRequestNext();
                        break;
                }
            }
            else
            {
                if (errCallback != null)
                    errCallback(www.error);
                else
                    CommonErrorHalder(url, www.error);

            }
            yield return null;
        }

        /// <summary>
        /// 停止所有加载
        /// </summary>
        public static void StopAllLoad()
        {
            _requestQueue.Clear();
            _currRequest = null;
            HideLoading();
        }

        /// <summary>
        /// 处理Http Failed的通用处理方法
        /// </summary>
        /// <param name="url"></param>
        /// <param name="msg"></param>
        public static void CommonFailedHalder(string url, string msg)
        {
            Debug.LogError(string.Format("[HTTP FAILED] {0}: {1}", url, msg));
            HideLoading();
            //TODO 通用失败显示及Retry
        }

        /// <summary>
        /// 处理Http Error的通用处理方法
        /// </summary>
        /// <param name="url"></param>
        /// <param name="msg"></param>
        public static void CommonErrorHalder(string url, string msg)
        {
            Debug.LogError(string.Format("[HTTP ERROR] {0}: {1}", url, msg));
            HideLoading();
            //TODO 通用错误显示及Retry
            UploadMessageLog(msg);
        }

        /// <summary>
        /// 上传日志
        /// </summary>
        /// <param name="message"></param>
        private static void UploadMessageLog(string message)
        {
            //TODO 日志上传
        }

        /// <summary>
        /// 显示加载
        /// </summary>
        /// <param name="text">显示文字</param>
        public static void showLoading(string text)
        {
            if (text == null)
            {
                text = Instance.showLoadingText;
            }

            if (Instance.loadingCtrl && !Instance.loadingCtrl.IsShow)
            {
                Instance.loadingCtrl.ShowLoading(text, Instance.loadingDisplayDelay);
            }
        }

        /// <summary>
        /// 隐藏加载
        /// </summary>
        public static void HideLoading()
        {
            if (Instance.loadingCtrl)
            {
                Instance.loadingCtrl.HideLoading();
            }
        }

        /// <summary>
        /// 检查是否隐藏加载
        /// </summary>
        private static void CheckToHideLoading()
        {
            if (Instance.loadingCtrl != null && Instance.loadingCtrl.IsShow && _requestQueue.Count <= 0)
            {
                HideLoading();
                //Debug.Log(string.Format("HideLoading({0})", _currRequest.args[2]));
            }
        }

        private Http()
        {
        }
    }


    public enum RequestType
    {
        JSON = 0,
        TEXT = 1,
        BSON = 2
    }

    public class Request
    {
        public RequestType requestType;
        public Http.Method method;
        public string url;
        public Dictionary<string, object> paramsDic;
        public Action<string> failCallback;
        public Action<string> errCallback;
        public bool isShowLoading;
        public string loadingText;

        public virtual void DoRequest() { }
    }

    public class Request<T> : Request where T : class
    {
        public Action<T> succCallback;

        public override void DoRequest()
        {
            CoroutineManager.Instance.DoAction(Http.DoRequest(method, requestType, url, paramsDic, succCallback, failCallback, errCallback, isShowLoading, loadingText));
        }

        public Request(RequestType requestType, Http.Method method, string url, Dictionary<string, object> paramsDic, Action<T> succCallback, Action<string> failCallback, Action<string> errCallback, bool isShowLoading, string loadingText)
        {
            this.requestType = requestType;
            this.method = method;
            this.url = url;
            this.paramsDic = paramsDic;
            this.succCallback = succCallback;
            this.failCallback = failCallback;
            this.errCallback = errCallback;
            this.isShowLoading = isShowLoading;
            this.loadingText = loadingText;
        }
    }

    public class HttpResult<T>
    {
        public enum ResType
        {
            SUCCESS = 0,
            FAILED = -1,
            NEED_AUTH = -2,
            WAIT = -3
        }

        /** 结果类型：成功、失败、需要登录、排队 */
        public ResType result;

        /** 其他数据 */
        public T data;

        /** 失败信息 */
        public string err;

        /** 错误信息 */
        public string error;
        public HttpResult()
        {
        }
        public HttpResult(ResType result, T data, string err, string error)
        {
            this.result = result;
            this.data = data;
            this.err = err;
            this.error = error;
        }
    }
}